var Map = require("js/map").Map;
var Match = require("js/match").Match;
var Obstacle = require("js/obstacle").Obstacle;
var Enemy = require("js/enemy").Enemy;
var Radar = require("js/radar").Radar;
var Turret = require("js/turret").Turret;
var gamejs = require("gamejs");
qModule("js/map");

var currentMatch = new Match();
var testMap = new Map(currentMatch, MAP_WIDTH, MAP_HEIGHT);

test("test for Map(match, width, height)[constructor]", function() {
	deepEqual(testMap.getSize(), [MAP_WIDTH, MAP_HEIGHT], "ok for valid dimensions [MAP_WIDTH, MAP_HEIGHT]");
	
	throws(function() {
		testMap = new Map(currentMatch, 0, 0);
	}, RangeError, "ok for invalid dimensions [0, 0][raise a RangeError]");
	
	throws(function() {
		testMap = new Map(currentMatch, -10, -10);
	}, RangeError, "ok for invalid dimensions [-10, -10][raise a RangeError]");
	
	throws(function() {
		testMap = new Map(undefined, 40, 40);
	}, TypeError, "ok for match = undefined[raise a TypeError]");
	
	throws(function() {
		testMap = new Map(null, 40, 40);
	}, TypeError, "ok for match = null[raise a TypeError]");
	
	testMap = new Map(currentMatch, 60, 40);
	var filled = false;
	for(var x = 0; (x < 60) && ( ! filled); x++){
		for(var y = 0; (y < 40) && ( ! filled); y++){
			filled = testMap.isFilled([x, y]);
		}
	}
	ok( ! filled,"ok for new empty map");
});

test("test for getMatch()", function() {
	strictEqual(testMap.getMatch(), currentMatch, "ok for the correct object returned");
});

test("test for getEnemies()", function() {
	strictEqual(testMap.getEnemies(), testMap.enemies, "ok for the correct object returned");
});

test("test for getObstacles()", function() {
	strictEqual(testMap.getObstacles(), testMap.obstacles, "ok for the correct object returned");
});

test("test for getTurrets()", function() {
	strictEqual(testMap.getTurrets(), testMap.turrets, "ok for the correct object returned");
});

test("test for getRadars()", function() {
	strictEqual(testMap.getRadars(), testMap.radars, "ok for the correct object returned");
});

test("test for getSize()", function() {
	deepEqual(testMap.getSize(), [60, 40], "ok for the correct size returned");
});

testMap = new Map(currentMatch, MAP_WIDTH, MAP_HEIGHT);
test("test for isInside([x, y])", function() {
	ok(testMap.isInside([10, 11]), "ok for internal coordinates [10, 11]");
	
	ok( ! testMap.isInside([-10, 11]), "ok for external coordinates [-10, 11]");
	
	ok(testMap.isInside([MAP_WIDTH - 1, 0]), "ok for marginal coordinates [799, 0]");
	
	ok( ! testMap.isInside([0, MAP_HEIGHT]), "ok for external boundary coordinates [0, 480]");
	
	throws(function() {
		testMap.isInside([-1]);
	}, TypeError, "ok for invalid coordinates [-1][raise a TypeError]");
	
	throws(function() {
		testMap.isInside([undefined, 10]);
	}, TypeError, "ok for invalid coordinates [undefined, 10][raise a TypeError]");
});

test("test for fill([x, y])", function() {
	testMap.matrix[1][1] = true;
	testMap.fill([1, 1]);
	ok(testMap.isFilled([1, 1]), "ok for valid coordinates [1, 1]");
	
	throws(function() {
		testMap.fill([-1, 0]);
	}, RangeError, "ok for invalid coordinates [-1, 0][raise a RangeError]");
	
	throws(function() {
		testMap.fill([undefined, 10]);
	}, TypeError, "ok for invalid coordinates [undefined, 10][raise a TypeError]");
});

test("test for clear([x, y])", function() {
	testMap.fill([1, 1]);
	testMap.clear([1, 1]);
	ok( ! testMap.isFilled([1, 1]), "ok for valid coordinates [1, 1]");
	
	throws(function() {
		testMap.clear([-1, 0]);
	}, RangeError, "ok for invalid coordinates [-1, 0][raise a RangeError]");
	
	throws(function() {
		testMap.clear([undefined, 10]);
	}, TypeError, "ok for invalid coordinates [undefined, 10][raise a TypeError]");
});

test("test for isFilled([x, y])", function() {
	strictEqual(testMap.isFilled([1, 1]), ! testMap.matrix[1][1], "ok for valid coordinates [1, 1]");
	
	throws(function() {
		testMap.isFilled([-1, 0]);
	}, RangeError, "ok for invalid coordinates [-1, 0][raise a RangeError]");
	
	throws(function() {
		testMap.isFilled([undefined, 10]);
	}, TypeError, "ok for invalid coordinates [undefined, 10][raise a TypeError]");
});

test("test for addObstacle(obstacle)", function() {
	var testMatch = new Match();
	testMatch.setMap(new Map(testMatch, 100, 80));
	var testObstacle = new Obstacle(testMatch, [25, 30], 20);
	testMap = new Map(currentMatch, 100, 80);
	
	throws(function() {
		testMap.addObstacle(undefined);
	}, TypeError, "ok for obstacle = undefined [raise a TypeError]");
	
	throws(function() {
		testMap.addObstacle(testObstacle);
	}, /InvalidOperationError/, "ok the adding of an obstacle with another Match[raise a InvalidOperationError]");
	
	testMap = new Map(currentMatch, 200, 200);
	currentMatch.setMap(testMap);
	testMap.addRadar(new Radar(currentMatch, [60, 80]));
	throws(function() {
		testMap.addObstacle(new Obstacle(currentMatch, [60, 80], 20));
	}, /InvalidPositionError/, "ok the adding of an obstacle with the underlying area already occupied[raise a InvalidPositionError]");
	
	testMap = new Map(currentMatch, 100, 80);
	currentMatch.setMap(testMap);
	var testObstacle = new Obstacle(currentMatch, [25, 30], 20);
	var oldLength = testMap.getObstacles().sprites().length;
	testMap.addObstacle(testObstacle);
	var newLength = testMap.getObstacles().sprites().length;
	strictEqual(newLength, oldLength + 1, "ok the adding of a valid obstacle");
	
	var isOk = true;
	for(var r = 0; (r < 80) && (isOk); r++) {
		for(var c = 0; (c < 100) && (isOk); c++) {
			var dx = 25 - c;
			var dy = 30 - r;
			var distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
			//leave a few pixels of tolerance for rounding
			if(distance < 20) {
				isOk = testMap.isFilled([c, r]);
			}
			else {
				if(distance > 21) {
					isOk = ! testMap.isFilled([c, r]);
				}
			}
		}
	}
	ok(isOk, "ok the filling for an internal obstacle, center [25, 30]; radius 20(in a [100, 80] map)");
});

test("test for removeObstacle(obstacle)", function() {
	testMap = new Map(currentMatch, 100, 80);
	currentMatch.setMap(testMap);
	var testObstacle = new Obstacle(currentMatch, [25, 30], 20);
	testObstacle.setSaleable(true);
	throws(function() {
		testMap.removeObstacle(testObstacle);
	}, /InvalidOperationError/, "ok the removing of an obstacle not added[raise a InvalidOperationError]");
	
	testMap = new Map(currentMatch, 100, 80);
	currentMatch.setMap(testMap);
	var testObstacle = new Obstacle(currentMatch, [25, 30], 20);
	testObstacle.setSaleable(true);
	testMap.addObstacle(testObstacle);
	var oldLength = testMap.getObstacles().sprites().length;
	testMap.removeObstacle(testObstacle);
	var newLength = testMap.getObstacles().sprites().length;
	strictEqual(newLength, oldLength - 1, "ok the removing of a valid obstacle");
	
	var isOk = true;
	for(var r = 0; (r < 80) && (isOk); r++) {
		for(var c = 0; (c < 100) && (isOk); c++) {
			isOk = ! testMap.isFilled([c, r]);
		}
	}
	ok(isOk, "ok the clearing for an obstacle, center [25, 30]; radius 20(in a [100, 80] map)");
});

test("test for addRadar(radar)", function() {
	currentMatch.changeCredit(5000);
	var testMatch = new Match();
	testMatch.setMap(new Map(testMatch, 300, 260));
	var testRadar = new Radar(testMatch, [55, 60]);
	testMap = new Map(currentMatch, 100, 80);
	
	throws(function() {
		testMap.addRadar(undefined);
	}, TypeError, "ok for radar = undefined [raise a TypeError]");
	
	throws(function() {
		testMap.addRadar(testRadar);
	}, /InvalidOperationError/, "ok the adding of a radar with another Match[raise a InvalidOperationError]");
	
	testMap = new Map(currentMatch, 200, 200);
	currentMatch.setMap(testMap);
	testMap.addObstacle(new Obstacle(currentMatch, [60, 80], 20));
	throws(function() {
		testMap.addRadar(new Radar(currentMatch, [60, 80]));
	}, /InvalidPositionError/, "ok the adding of a radar with the underlying area already occupied[raise a InvalidPositionError]");
	
	currentMatch.changeCredit(-currentMatch.credit);
	testMap = new Map(currentMatch, 200, 200);
	currentMatch.setMap(testMap);
	throws(function() {
		testMap.addRadar(new Radar(currentMatch, [60, 80]));
	}, /InvalidOperationError/, "ok the adding of a radar with not enough credits [raise a InvalidOperationError]");
	
	currentMatch.changeCredit(5000);
	testMap = new Map(currentMatch, 300, 260);
	currentMatch.setMap(testMap);
	var testRadar = new Radar(currentMatch, [55, 60]);
	var oldLength = testMap.getRadars().sprites().length;
	testMap.addRadar(testRadar);
	var newLength = testMap.getRadars().sprites().length;
	strictEqual(newLength, oldLength + 1, "ok the adding of a valid radar");
	
	var testRadar = new Radar(currentMatch, [55, 60]);
	testMap = new Map(currentMatch, 300, 260);
	testMap.addRadar(testRadar);
	var obstructiveRadius = testRadar.getObstructiveRadius();
	var isOk = true;
	for(var y = 0; (y < 260) && (isOk); y++) {
		for(var x = 0; (x < 300) && (isOk); x++) {
			var dx = 55 - x;
			var dy = 60 - y;
			if(Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)) <= obstructiveRadius) {
				isOk = testMap.isFilled([x, y]);
			}
			else {
				isOk = ! testMap.isFilled([x, y]);
			}
			//leaves a little tolerance
			if(( ! isOk) &&
				Math.abs(obstructiveRadius - Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2))) <= 0.001) {
				isOk = true;
			}
		}
	}
	ok(isOk, "ok the filling for an internal radar, center [55, 60]; (in a [300, 260] map)");
});

test("test for removeRadar(radar)", function() {
	currentMatch.changeCredit(5000);
	testMap = new Map(currentMatch, 300, 260);
	currentMatch.setMap(testMap);
	var testRadar = new Radar(currentMatch, [55, 60]);
	throws(function() {
		testMap.removeRadar(testRadar);
	}, /InvalidOperationError/, "ok the removing of a radar not added[raise a InvalidOperationError]");
	
	testMap = new Map(currentMatch, 300, 260);
	currentMatch.setMap(testMap);
	var testRadar = new Radar(currentMatch, [55, 60]);
	testMap.addRadar(testRadar);
	var oldLength = testMap.getRadars().sprites().length;
	testMap.removeRadar(testRadar);
	var newLength = testMap.getRadars().sprites().length;
	strictEqual(newLength, oldLength - 1, "ok the removing of a valid radar");
	
	var isOk = true;
	for(var r = 0; (r < 260) && (isOk); r++) {
		for(var c = 0; (c < 300) && (isOk); c++) {
			isOk = ! testMap.isFilled([c, r]);
		}
	}
	ok(isOk, "ok the clearing for a radar, center [55, 60]; (in a [300, 260] map)");
});

test("test fot hasRadars()", function() {
	currentMatch.changeCredit(5000);
	testMap = new Map(currentMatch, 300, 260);
	currentMatch.setMap(testMap);
	var testRadar = new Radar(currentMatch, [55, 60]);
	testMap.addRadar(testRadar);
	ok(testMap.hasRadars(), "correctly return true");
	testMap.removeRadar(testRadar);
	ok(!testMap.hasRadars(), "correctly return false");
});

test("test for addTurret(turret)", function() {
	currentMatch.changeCredit(5000);
	var testMatch = new Match();
	testMatch.setMap(new Map(testMatch, 300, 260));
	var testTurret = new Turret(testMatch, [55, 60]);
	testMap = new Map(currentMatch, 100, 80);
	
	throws(function() {
		testMap.addTurret(undefined);
	}, TypeError, "ok for turret = undefined [raise a TypeError]");
	
	throws(function() {
		testMap.addTurret(testTurret);
	}, /InvalidOperationError/, "ok the adding of a turret with another Match[raise a InvalidOperationError]");
	
	testMap = new Map(currentMatch, 200, 200);
	currentMatch.setMap(testMap);
	testMap.addObstacle(new Obstacle(currentMatch, [60, 80], 20));
	throws(function() {
		testMap.addTurret(new Turret(currentMatch, [60, 80]));
	}, /InvalidPositionError/, "ok the adding of a turret with the underlying area already occupied[raise a InvalidPositionError]");
	
	currentMatch.changeCredit(-currentMatch.credit);
	testMap = new Map(currentMatch, 200, 200);
	currentMatch.setMap(testMap);
	throws(function() {
		testMap.addTurret(new Turret(currentMatch, [60, 80]));
	}, /InvalidOperationError/, "ok the adding of a turret with not enough credits [raise a InvalidOperationError]");
	
	currentMatch.changeCredit(5000);
	testMap = new Map(currentMatch, 300, 260);
	currentMatch.setMap(testMap);
	var testTurret = new Turret(currentMatch, [55, 60]);
	var oldLength = testMap.getTurrets().sprites().length;
	testMap.addTurret(testTurret);
	var newLength = testMap.getTurrets().sprites().length;
	strictEqual(newLength, oldLength + 1, "ok the adding of a valid turret");
	
	var testTurret = new Turret(currentMatch, [55, 60]);
	testMap = new Map(currentMatch, 300, 260);
	testMap.addTurret(testTurret);
	var obstructiveRadius = testTurret.getObstructiveRadius();
	var isOk = true;
	for(var y = 0; (y < 260) && (isOk); y++) {
		for(var x = 0; (x < 300) && (isOk); x++) {
			var dx = 55 - x;
			var dy = 60 - y;
			if(Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)) <= obstructiveRadius) {
				isOk = testMap.isFilled([x, y]);
			}
			else {
				isOk = ! testMap.isFilled([x, y]);
			}
			//leaves a little tolerance
			if(( ! isOk) &&
				Math.abs(obstructiveRadius - Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2))) <= 0.001) {
				isOk = true;
			}
		}
	}
	ok(isOk, "ok the filling for an internal turret, center [55, 60]; (in a [300, 260] map)");
});

test("test for removeTurret(turret)", function() {
	currentMatch.changeCredit(5000);
	testMap = new Map(currentMatch, 300, 260);
	currentMatch.setMap(testMap);
	var testTurret = new Turret(currentMatch, [55, 60]);
	throws(function() {
		testMap.removeTurret(testTurret);
	}, /InvalidOperationError/, "ok the removing of a turret not added[raise a InvalidOperationError]");
	
	testMap = new Map(currentMatch, 300, 260);
	currentMatch.setMap(testMap);
	var testTurret = new Turret(currentMatch, [55, 60]);
	testMap.addTurret(testTurret);
	var oldLength = testMap.getTurrets().sprites().length;
	testMap.removeTurret(testTurret);
	var newLength = testMap.getTurrets().sprites().length;
	strictEqual(newLength, oldLength - 1, "ok the removing of a valid turret");
	
	var isOk = true;
	for(var r = 0; (r < 260) && (isOk); r++) {
		for(var c = 0; (c < 300) && (isOk); c++) {
			isOk = ! testMap.isFilled([c, r]);
		}
	}
	ok(isOk, "ok the clearing for a turret, center [55, 60]; (in a [300, 260] map)");
});

test("test fot hasTurrets()", function() {
	currentMatch.changeCredit(5000);
	testMap = new Map(currentMatch, 300, 260);
	currentMatch.setMap(testMap);
	var testTurret = new Turret(currentMatch, [55, 60]);
	testMap.addTurret(testTurret);
	ok(testMap.hasTurrets(), "correctly return true");
	testMap.removeTurret(testTurret);
	ok(!testMap.hasTurrets(), "correctly return false");
});

test("test for addEnemy(enemy)", function() {
	var testMatch = new Match();
	testMatch.setMap(new Map(testMatch, MAP_WIDTH, MAP_HEIGHT));
	var testEnemy = new Enemy(testMatch, [25, 30]);
	testMap = new Map(currentMatch, MAP_WIDTH, MAP_HEIGHT);
	
	throws(function() {
		testMap.addEnemy(undefined);
	}, TypeError, "ok for enemy = undefined [raise a TypeError]");
	
	throws(function() {
		testMap.addEnemy(testEnemy);
	}, /InvalidOperationError/, "ok the adding of an enemy with another Match[raise a InvalidOperationError]");
	
	testMap = new Map(currentMatch, MAP_WIDTH, MAP_HEIGHT);
	currentMatch.setMap(testMap);
	var testEnemy = new Enemy(currentMatch, [25, 30]);
	var oldLength = testMap.getEnemies().sprites().length;
	testMap.addEnemy(testEnemy);
	var newLength = testMap.getEnemies().sprites().length;
	strictEqual(newLength, oldLength + 1, "ok the adding of a valid enemy");
});

test("test for removeEnemy(enemy)", function() {
	testMap = new Map(currentMatch, MAP_WIDTH, MAP_HEIGHT);
	currentMatch.setMap(testMap);
	var testEnemy = new Enemy(currentMatch, [25, 30]);
	throws(function() {
		testMap.removeEnemy(testEnemy);
	}, /InvalidOperationError/, "ok the removing of an enemy not added[raise a InvalidOperationError]");
	
	testMap = new Map(currentMatch, MAP_WIDTH, MAP_HEIGHT);
	currentMatch.setMap(testMap);
	var testEnemy = new Enemy(currentMatch, [25, 30]);
	testMap.addEnemy(testEnemy);
	var oldLength = testMap.getEnemies().sprites().length;
	testMap.removeEnemy(testEnemy);
	var newLength = testMap.getEnemies().sprites().length;
	strictEqual(newLength, oldLength - 1, "ok the removing of a valid enemy");
});

test("test for adjacent(coordinates)", function() {
	testMap = new Map(currentMatch, 100, 80);
	currentMatch.setMap(testMap);
	
	throws(function() {
		testMap.adjacent([-1, 0]);
	}, RangeError, "ok for invalid coordinates [-1, 0][raise a RangeError]");
	
	throws(function() {
		testMap.adjacent([undefined, 10]);
	}, TypeError, "ok for invalid coordinates [undefined, 10][raise a TypeError]");
	
	var isOk = true;
	var startPoint = [10, 5];
	var adjacent =  testMap.adjacent(startPoint);
	for(i = 0; (i < adjacent.length) && (isOk); i++) {
		isOk = testMap.isInside(adjacent[i]) && ! testMap.isFilled(adjacent[i]);
		if(isOk) {
			var dx = Math.abs(startPoint[X] - (adjacent[i])[X]);
			var dy = Math.abs(startPoint[Y] - (adjacent[i])[Y]);
			isOk = ! ((dx > 1) || (dy > 1));
		}
	}
	ok(isOk, "ok for valid coordinates [10, 5]");
	
	var isOk = true;
	var startPoint = [10, 0];
	var adjacent =  testMap.adjacent(startPoint);
	for(i = 0; (i < adjacent.length) && (isOk); i++) {
		isOk = testMap.isInside(adjacent[i]) && ! testMap.isFilled(adjacent[i]);
		if(isOk) {
			var dx = Math.abs(startPoint[X] - (adjacent[i])[X]);
			var dy = Math.abs(startPoint[Y] - (adjacent[i])[Y]);
			isOk = ! ((dx > 1) || (dy > 1));
		}
	}
	ok(isOk, "ok for valid marginal coordinates [10, 0]");
});

test("test for reachable(coordinates, stepLength)", function() {
	testMap = new Map(currentMatch, 100, 80);
	currentMatch.setMap(testMap);
	var stepLength = 5;
	
	throws(function() {
		testMap.reachable([-1, 0], stepLength);
	}, RangeError, "ok for invalid coordinates [-1, 0][raise a RangeError]");
	
	throws(function() {
		testMap.reachable([undefined, 10], stepLength);
	}, TypeError, "ok for invalid coordinates [undefined, 10][raise a TypeError]");
	
	var isOk = true;
	var startPoint = [10, 15];
	var reachable =  testMap.reachable(startPoint, stepLength);
	for(i = 0; (i < reachable.length) && (isOk); i++) {
		isOk = testMap.isInside(reachable[i]) && ! testMap.isFilled(reachable[i]);
		if(isOk) {
			var dx = Math.abs(startPoint[X] - (reachable[i])[X]);
			var dy = Math.abs(startPoint[Y] - (reachable[i])[Y]);
			isOk = ! ((dx > stepLength) || (dy > stepLength));
		}
	}
	ok(isOk, "ok for valid coordinates [10, 15]");
	
	var isOk = true;
	var startPoint = [10, 0];
	var reachable =  testMap.reachable(startPoint, stepLength);
	for(i = 0; (i < reachable.length) && (isOk); i++) {
		isOk = testMap.isInside(reachable[i]) && ! testMap.isFilled(reachable[i]);
		if(isOk) {
			var dx = Math.abs(startPoint[X] - (reachable[i])[X]);
			var dy = Math.abs(startPoint[Y] - (reachable[i])[Y]);
			isOk = ! ((dx > stepLength) || (dy > stepLength));
		}
	}
	ok(isOk, "ok for valid marginal coordinates [10, 0]");
});

test("test for actualDistance(startPoint, endPoint)", function() {
	testMap = new Map(currentMatch, 100, 80);
	currentMatch.setMap(testMap);
	
	throws(function() {
		testMap.actualDistance([-1, 0], [10, 0]);
	}, RangeError, "ok for invalid startPoint coordinates [-1, 0][raise a RangeError]");
	
	throws(function() {
		testMap.actualDistance([10, 0], [-1, 0]);
	}, RangeError, "ok for invalid endPoint coordinates [-1, 0][raise a RangeError]");
	
	throws(function() {
		testMap.actualDistance([undefined, 10], [-1, 0]);
	}, TypeError, "ok for invalid startPoint coordinates [undefined, 10][raise a TypeError]");
	
	var isOk = true;
	var startPoint = [10, 20];
	var adjacent =  testMap.adjacent(startPoint);
	for(i = 0; (i < adjacent.length) && (isOk); i++) {
		var dx = startPoint[X] - (adjacent[i])[X];
		var dy = startPoint[Y] - (adjacent[i])[Y];
		var actDistance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
		isOk = (actDistance.toFixed(2) == testMap.actualDistance(startPoint, adjacent[i]));
	}
	ok(isOk, "ok for valid coordinates [10, 20]");
});

test("test for estimatedDistance(startPoint, endPoint)", function() {
	testMap = new Map(currentMatch, 100, 80);
	currentMatch.setMap(testMap);
	
	throws(function() {
		testMap.estimatedDistance([-1, 0], [10, 0]);
	}, RangeError, "ok for invalid startPoint coordinates [-1, 0][raise a RangeError]");
	
	throws(function() {
		testMap.estimatedDistance([10, 0], [-1, 0]);
	}, RangeError, "ok for invalid endPoint coordinates [-1, 0][raise a RangeError]");
	
	throws(function() {
		testMap.estimatedDistance([undefined, 10], [-1, 0]);
	}, TypeError, "ok for invalid startPoint coordinates [undefined, 10][raise a TypeError]");
	
	var isOk = true;
	var startPoint = [33, 40];
	var reachable =  testMap.reachable(startPoint, 21);
	for(i = 0; (i < reachable.length) && (isOk); i++) {
		var dx = Math.abs(startPoint[X] - (reachable[i])[X]);
		var dy = Math.abs(startPoint[Y] - (reachable[i])[Y]);
		isOk = (dx + dy === testMap.estimatedDistance(startPoint, reachable[i]));
	}
	ok(isOk, "ok for points distanced by 21 from valid coordinates [33, 40]");
});